package cn.ac.iie.ban.data.exchange.runner.tokenBucket.util;

import cn.ac.iie.ban.data.exchange.runner.tokenBucket.exception.DEFCInitException;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;

public class HttpHelper {

    private static final String HTTP_URI_PREFIX = "http:////";
    private static final String HTTPS_URI_PREFIX = "https:////";
    private static final int DEFAULT_TIMEOUT = 5;
    private static final String SPLITTER = "/";

    private String uriPrefix;
    int timeout;

    public HttpHelper(boolean TLS) throws DEFCInitException {
        this(TLS, DEFAULT_TIMEOUT);
    }

    public HttpHelper(boolean TLS, int timeoutSec) throws DEFCInitException {
        if (TLS) {
            try {
                initHTTPS();
            } catch (IOException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException e) {
                throw new DEFCInitException("cannot init httpHelper.", e);
            }
            uriPrefix = HTTPS_URI_PREFIX;
        } else {
            initHTTP();
            uriPrefix = HTTP_URI_PREFIX;
        }
        this.timeout = timeoutSec * 1000;
    }

    private CloseableHttpClient client = null;

    private void initHTTP() {
        if (client == null) {
            synchronized (HttpHelper.class) {
                if (client == null) {
                    client = HttpClients.createDefault();
                }
            }
        }
    }

    private void initHTTPS() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, CertificateException, UnrecoverableKeyException {

        if (client == null) {
            synchronized (HttpHelper.class) {
                if (client == null) {

                    //key manager
                    KeyStore keyStore = KeyStore.getInstance("JKS");
                    byte[] bs = Certs.getClientPrivatKeystoreBytes();
                    ByteArrayInputStream bis = new ByteArrayInputStream(bs);
                    keyStore.load(bis, Certs.getKeyStorePasswd());

                    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
                    kmf.init(keyStore, Certs.getKeyPasswd());
                    KeyManager[] km = kmf.getKeyManagers();

                    //trust manager
                    KeyStore trustStore = KeyStore.getInstance("JKS");
                    bs = Certs.getServerPublicKeystoreBytes();
                    bis = new ByteArrayInputStream(bs);
                    trustStore.load(bis, Certs.getTrustStorePasswd());

                    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
                    tmf.init(trustStore);
                    TrustManager[] tm = tmf.getTrustManagers();

                    //ssl context
                    SSLContext sslContext = SSLContext.getInstance("TLS");
                    sslContext.init(km, tm, null);

                    client = HttpClients.custom()
                            .setSSLContext(sslContext)
                            .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                            .build();
                }
            }
        }
    }

    public Result get(Collection<String> urls, String uriSuffix) throws IOException {
        return get(client, urls, uriPrefix, uriSuffix, timeout);
    }

    public Result get(String url) throws IOException {
        return get(client, url, timeout);
    }

    public Result post(Collection<String> urls, String uriSuffix, Map<String, String> paramPair) throws IOException {
        return post(client, urls, uriPrefix, uriSuffix, paramPair, timeout);
    }

    public Result post(String url, Map<String, String> paramPair) throws IOException {
        return post(client, url, paramPair, timeout);
    }

    public void close() throws IOException {
        this.client.close();
    }

    public static Result get(CloseableHttpClient client, Collection<String> urls, String uriPrefix, String uriSuffix) throws IOException {
        return get(client, urls, uriPrefix, uriSuffix, DEFAULT_TIMEOUT);
    }

    public static Result get(CloseableHttpClient client, Collection<String> urls, String uriPrefix, String uriSuffix, int timtoutSec) throws IOException {
        ArrayList<String> uris = new ArrayList<>(urls.size());
        for (String url : urls) {
            uris.add((uriPrefix + url + SPLITTER + uriSuffix).replace(SPLITTER + SPLITTER, SPLITTER));
        }
        Collections.shuffle(uris);
        Result result = null;
        IOException lastException = null;
        for (String uri : uris) {
            try {
                result = get(client, uri, timtoutSec);
            } catch (IOException ex) {
                lastException = ex;
            }
        }
        if (result == null) {
            throw lastException;
        }
        return result;
    }

    public static Result get(CloseableHttpClient client, String url) throws IOException {
        return get(client, url, DEFAULT_TIMEOUT);
    }

    public static Result get(CloseableHttpClient client, String url, int timeoutSec) throws UnsupportedEncodingException, IOException {

        if (url.startsWith("http://") || url.startsWith("HTTP://") || url.startsWith("https://") || url.startsWith("HTTPS://")) {
        } else {
            url = "http://" + url;
        }
        if (url.endsWith("/")) {
        } else {
            url += "/";
        }
        HttpGet get = new HttpGet(url);
        get.setConfig(RequestConfig.custom().setConnectTimeout(timeoutSec).setSocketTimeout(timeoutSec).setConnectionRequestTimeout(timeoutSec).build());
        HttpResponse response = client.execute(get);
        Result rslt = new Result();
        rslt.setStatusCode(response.getStatusLine().getStatusCode());
        rslt.setReasonPhrase(response.getStatusLine().getReasonPhrase());
        rslt.setReplyInputStream(response.getEntity().getContent());
        return rslt;
    }

    public static Result post(CloseableHttpClient client, Collection<String> urls, String uriPrefix, String uriSuffix, Map<String, String> paramPair) throws IOException {
        return post(client, urls, uriPrefix, uriSuffix, paramPair, DEFAULT_TIMEOUT);
    }

    public static Result post(CloseableHttpClient client, Collection<String> urls, String uriPrefix, String uriSuffix, Map<String, String> paramPair, int timtoutSec) throws IOException {
        ArrayList<String> uris = new ArrayList<>(urls.size());
        for (String url : urls) {
            uris.add((uriPrefix + url + SPLITTER + uriSuffix).replace(SPLITTER + SPLITTER, SPLITTER));
        }
        Collections.shuffle(uris);
        Result result = null;
        IOException lastException = null;
        for (String uri : uris) {
            try {
                result = post(client, uri, paramPair, timtoutSec);
            } catch (IOException ex) {
                lastException = ex;
            }
        }
        if (result == null) {
            throw lastException;
        }
        return result;
    }

    public static Result post(CloseableHttpClient client, String url, Map<String, String> paramPair) throws IOException {
        return post(client, url, paramPair, DEFAULT_TIMEOUT);
    }

    public static Result post(CloseableHttpClient client, String url, Map<String, String> paramPair, int timeoutSec) throws UnsupportedEncodingException, IOException {

        if (url.startsWith("http://") || url.startsWith("HTTP://") || url.startsWith("https://") || url.startsWith("HTTPS://")) {
        } else {
            url = "http://" + url;
        }
        if (url.endsWith("/")) {
        } else {
            url += "/";
        }
        HttpPost post = new HttpPost(url);
        post.setConfig(RequestConfig.custom().setConnectTimeout(timeoutSec).setSocketTimeout(timeoutSec).setConnectionRequestTimeout(timeoutSec).build());
        if (paramPair != null) {
            List<NameValuePair> params = new ArrayList<>(paramPair.size());
            for (Map.Entry<String, String> entry : paramPair.entrySet()) {
                params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            post.setEntity(new UrlEncodedFormEntity(params));
        }
        HttpResponse response = client.execute(post);
        Result rslt = new Result();
        rslt.setStatusCode(response.getStatusLine().getStatusCode());
        rslt.setReasonPhrase(response.getStatusLine().getReasonPhrase());
        rslt.setReplyInputStream(response.getEntity().getContent());
        return rslt;
    }

    public static class Result {

        public int getStatusCode() {
            return statusCode;
        }

        public void setStatusCode(int statusCode) {
            this.statusCode = statusCode;
        }

        public String getReasonPhrase() {
            return reasonPhrase;
        }

        public void setReasonPhrase(String reasonPhrase) {
            this.reasonPhrase = reasonPhrase;
        }

        public String getContent() throws IOException {
            if (content == null) {
                synchronized (this) {
                    if (content == null) {
                        BufferedReader br = new BufferedReader(new InputStreamReader(is));
                        String line;
                        while ((line = br.readLine()) != null) {
                            if (content == null) {
                                content = line;
                            } else {
                                content += "\n" + line;
                            }
                        }
                        br.close();
                        is.close();
                    }
                }
            }
            return content;
        }

        public void setReplyInputStream(InputStream is) {
            this.is = is;
        }

        public InputStream getReplyInputStream() {
            return is;
        }

        private int statusCode;
        private String reasonPhrase;
        private String content;
        private InputStream is;

        public Result() {
            statusCode = 0;
            reasonPhrase = "";
            content = null;
            is = null;
        }

        @Override
        public String toString() {
            try {
                return "{statusCode=" + getStatusCode() + ", reasonPhrase=" + getReasonPhrase() + ", content=" + getContent() + "}";
            } catch (IOException ex) {
                return "{statusCode=" + getStatusCode() + ", reasonPhrase=" + getReasonPhrase() + ", content=" + ex.getMessage() + "}";
            }
        }

    }

}
